<?php declare(strict_types=1);

namespace App\Core;


use App\Core\ModelExtend\Custom;
use App\Core\ModelExtend\FileColumn;
use Hyperf\Database\Model\Model as baseModel;
use Hyperf\Utils\Str;

/**
 * @method  static \Hyperf\Database\Query\Builder join
 * @method  static \Hyperf\Database\Query\Builder whereIn
 * @method  static \Hyperf\Database\Query\Builder where
 * @method  static \Hyperf\Database\Query\Builder first
 * @see \Hyperf\Database\Query\Builder
 */
abstract class Model extends baseModel
{
    use \Hyperf\Database\Model\Concerns\CamelCase;

    use FileColumn;

    use Custom;

    protected array $manyToManyChange = []; //对多字段变化记录

    public const MAPPING_TYPE_MANY_TO_MANY = 'many_to_many';

    public const MAPPING_TYPE_NONE_TO_MANY = 'one_to_many';

    public const MAPPING_TYPE_ONE_TO_ONE = 'one_to_one';

    public const DELETE_TYPE_DELETE = 'delete'; //逐个删除并递归检查依赖管理

    public const DELETE_TYPE_DELETE_BATCH = 'delete_batch'; //批量删除不会检查删除对象的依赖关系

    public const DELETE_TYPE_TIPS = 'tips'; // 删除提醒

    public function __construct(array $attributes = [])
    {
        //重写系统参数
        parent::__construct($attributes);
        $this->table = static::TABLE;
        $this->incrementing = true;
        //初始化赋值
        $this->getCasts();
    }

    /**
     * 动态获取表关系(这句是重新了框架的方法)
     * @param string $method
     * @return NULL|mixed
     */
    protected function getRelationshipFromMethod($method)
    {
        $relation = $this->getRelation($method);
        if (!$relation) {
            return null;
        }

        return tap($relation->getResults(), function ($results) use ($method) {
            $this->setRelation($method, $results);
        });
    }

    /**
     * 获取关联关系
     * @param string $column
     * @return object
     */
    public function getRelation($column)
    {
        $relationObj = null;
        foreach (static::COLUMN as $config) {
            if (!isset($config['relation'])) {
                continue;
            }

            $relation = [];
            //对应多个模型
            if (isset($config['relation'][0])) {
                $query = false;
                foreach ($config['relation'] as $_config) {
                    if ($_config['alias'] != $column || $this->{$_config['filter']['column']} != $_config['filter']['value']) {
                        continue;
                    }
                    $query = true;
                    $relation = $_config;
                    break;
                }

                if (!$query) {
                    continue;
                }
            } else {
                $relation = $config['relation'];
            }

            if (isset($relation['alias']) && $column != $relation['alias']) {
                continue;
            }

            //如果没有设置别名就用引用对象的模型名称
            if (!isset($relation['alias']) && $relation['config'][0]::TABLE != $column) {
                continue;
            }

            switch ($relation['type']) {
                case 'belongs_to':
                    $relationObj = $this->belongsTo(...$relation['config']);
                    break;
                case 'one_to_one':
                    $relationObj = $this->hasOne(...$relation['config']);
                    break;
                case 'one_to_many':
                    $relationObj = $this->hasMany(...$relation['config']);
                    break;
                case 'many_to_many':
                    $_config = $relation['config'];
                    $_config[1] = $relation['config'][1]::TABLE;
                    $relationObj = $this->belongsToMany(...$_config);
                    break;
            }

            if (isset($relation['where'])) {
                foreach ($relation['where'] as $column => $item) {
                    $relationObj->where($column, $item);
                }
            }

            break;
        }

        return $relationObj;
    }

    /**
     * 组装格式化数据(系统方法)
     * @param string $name
     * @return mixed
     */
    public function getCasts()
    {
        $casts = [];
        foreach (static::COLUMN as $name => $column) {
            if (empty($column['type'])) {
                continue;
            }
            $type = explode('|', $column['type']);
            $casts[$name] = $type[0];
        }
        return $casts;
    }

    /**
     * 字段名称配置
     * @param string|null $column
     * @return array|string
     */
    public static function getColumnName(?string $column = null): array|string
    {
        $columns = [];
        foreach (static::COLUMN as $_column => $item) {
            $columns[$_column] = $item['name'];
        }

        if ($column) {
            return $columns[$column];
        }

        return $columns;
    }

    /**
     * 获取所有的数据表字段
     * @param boolean $tablePrefix 是否需要带上表前缀
     * @param array $filter 过滤掉的字段
     * @return array
     */
    public static function getFullColumns(bool $tablePrefix = false, array $filter = []): array
    {
        $columns = [];
        foreach (static::COLUMN as $column => $item) {
            if ($tablePrefix) {
                $columns[] = static::TABLE . '.' . $column;
            } else {
                $columns[] = $column;
            }
        }

        return $columns;
    }

    /**
     * 更改公共逻辑, 替换编辑器中上传的文件
     * @param string $key
     * @return \Hyperf\Utils\HigherOrderTapProxy|mixed|void
     */
    public function getAttribute($key)
    {
        $content = parent::getAttribute($key);
        if ($content === null) {
            $key = Str::snake($key);
            $content = parent::getAttribute($key);
        }

        if ($content) {
            //编辑器文件路径替换
            $isEditor = static::COLUMN[$key]['is_editor'] ?? false;
            if ($isEditor) {
                $content = str_replace('{storage_base_url}', config('storage_base_url'), $content);
            }
        }

        return $content;
    }

    /**
     *  获取多对多字段变化
     * @return array
     */
    public function getManyToManyChanges(): array
    {
        return $this->manyToManyChange;
    }

    /**
     * Handle dynamic method calls into the model.
     *
     * @param string $method
     * @param array $parameters
     * @return mixed
     */
    public function __call($method, $parameters)
    {
        $relationship = $this->getRelation($method);
        if ($relationship) {
            return $relationship;
        }
        return parent::__call($method, $parameters);
    }

    /**
     * 获取模型对应的对象关联对象名称
     * @param string $class
     * @return string
     */
    protected function getModelRelationObjectName($class)
    {
        $tA = explode('\\', $class);
        $name = end($tA);
        return lcfirst(str_replace('Model', '', $name));
    }

    /**
     * 重写系统中的关系映射方式
     * Get a relationship.
     *
     * @param string $key
     */
    public function getRelationValue($key)
    {
        // If the key already exists in the relationships array, it just means the
        // relationship has already been loaded, so we'll just return it out of
        // here because there is no need to query within the relations twice.
        if ($this->relationLoaded($key)) {
            return $this->relations[$key];
        }

        // If the "attribute" exists as a method on the model, we will just assume
        // it is a relationship and will load and return results from the query
        // and hydrate the relationship's value on the "relationships" array.
        return $this->getRelationshipFromMethod($key);
    }

    /**
     * Fill the model with an array of attributes.
     *
     * @return $this
     * @throws \Hyperf\Database\Model\MassAssignmentException
     */
    public function fill(array $attributes)
    {
        $totallyGuarded = $this->totallyGuarded();

        foreach ($this->fillableFromArray($attributes) as $key => $value) {
            $key = $this->removeTableFromKey($key);

            // The developers may choose to place some attributes in the "fillable" array
            // which means only those attributes may be set through mass assignment to
            // the model, and all others will just get ignored for security reasons.
            $this->setAttribute($key, $value);
        }

        return $this;
    }

    /**
     *  这里重写核心代码为了让原始数据一致保存到最后一次修改
     * Perform any actions that are necessary after the model is saved.
     */
    protected function finishSave(array $options)
    {
        $this->fireModelEvent('saved');

        if ($this->isDirty() && ($options['touch'] ?? true)) {
            $this->touchOwners();
        }

        if (!$this->exists) {
            $this->syncOriginal();
        }
    }

    /**
     * 删除属性
     * @param string $key
     */
    public function __unset($key)
    {
        $this->offsetUnset(Str::snake($key));
    }


    /**
     * Decode the given JSON back into an array or object.
     *
     * @param string $value
     * @param bool $asObject
     */
    public function fromJson($value, $asObject = false)
    {
        $tO = json_decode($value, ! $asObject);
        if (  ! $asObject && ! is_array($tO)) {
            return [];
        }
        return $tO;
    }
}
